package db

import (
	"time"
	"fmt"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const (
	AUTOUUID 			= "autouuid"	// 自增 id 表
)

var DBAddr = ""
var DBName = ""

var _session *mgo.Session = nil

func GetDB() (*mgo.Database, *mgo.Session, error) {
	if _session == nil {
		//sin, err := mgo.Dial(Config.DB)
		sin, err := mgo.Dial(DBAddr)
		if err != nil {
			return nil, nil, err
		}
		_session = sin
	}
	session := _session.Clone()
	session.SetMode(mgo.Monotonic, true)
	//db := session.DB(Config.DBName)
	db := session.DB(DBName)
	return db, session, nil
}

// 数组更新结构
type updateArray struct {
	op 		string	// 操作
	name	string	// 数组的名称
	i 		interface{}	// 数据

	qKey	string	// 查询所使用的key
	qI 		interface{}	// 查询所使用的数据
}

// 获得数组更新的语句，返回查询条件和修改
func (u *updateArray) sql(id bson.ObjectId) (bson.M, bson.M) {
	ret := bson.M{u.op: bson.M{u.name: u.i}}
	if nil == u.qI {
		return bson.M{"_id": id}, ret
	}
	return bson.M{"_id": id, u.qKey: u.qI}, ret
}

// 数据表更新器
type DBUpdater struct {
	id 		bson.ObjectId
	updater map[string]interface{}
	i 		interface{}
	isRemove	bool
	isInsert	bool

	arrays 	[]*updateArray
}

func (u *DBUpdater) Update(i interface{}, name string) {
	if nil != i {
		if "" == name {
			u.i = i
		} else {
			u.updater[name] = i
		}
	}
}

// 更新数据结构
type updaterData struct {
	table	string
	id 		bson.ObjectId
	i 		interface{}
	name	string
	isRemove	bool	// 是否要删除
	isInsert	bool

	// 对数组的操作
	arrays 	[]*updateArray
}

// 数据库操作缓存
type DBCache struct {
	// 调用 UpdateId 的列表
	updateIds		map[string][]*DBUpdater

	p 				chan *updaterData 	// 更新数据接收信道
	closeSig 		chan bool
	flushSig		chan bool 			// 请求刷新的消息信道
}

func NewDBCache() *DBCache {
	db := &DBCache{
		make(map[string][]*DBUpdater), 
		make(chan *updaterData, 1000),
		make(chan bool, 1),
		make(chan bool, 1),
	}
	go func() {
		defer func() {
			time.Sleep(time.Second)
			db.closeSig <- true
		} ()

		t := time.NewTicker(time.Minute)
		defer t.Stop()
		for {
			select {
			case d, ok := <- db.p:
				if ok {
					db._updateId(d)
				} else {
					db._flush()
					return
				}
			case <- t.C:
				db._flush()
			case <- db.flushSig:
				db._flush()
			}
		}
	} ()
	return db
}

// 插入数据
func (db *DBCache) Insert(table string, id bson.ObjectId, i interface{}) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	} ()
	db.p <- &updaterData{table, id, i, "", false, true, nil}
}

// 更新数据表 table
// id:	要更新的数据选择器
// i:	要更新的数据
// name:	该数据的名称，如果为空则表示是更新整个数据
func (db *DBCache) UpdateId(table string, id bson.ObjectId, i interface{}, name string) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	} ()
	if len(db.p) >= 900 {
		fmt.Println("等待更新的数据缓冲区达到警戒值", len(db.p))
	}
	db.p <- &updaterData{table, id, i, name, false, false, nil}
}

func (db *DBCache) RemoveId(table string, id bson.ObjectId) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	} ()
	db.p <- &updaterData{table, id, nil, "", true, false, nil}
}

// 添加数组元素
func (db *DBCache) PushArray(table string, id bson.ObjectId, i interface{}, name string) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	} ()
	u := &updaterData{table, id, nil, "", false, false, nil}
	u.arrays = append(u.arrays, &updateArray{"$push", name, i, "", nil})
	db.p <- u
}

// 删除数组元素
func (db *DBCache) PullArray(table string, id bson.ObjectId, i interface{}, name string) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	} ()
	u := &updaterData{table, id, nil, "", false, false, nil}
	u.arrays = append(u.arrays, &updateArray{"$pull", name, i, "", nil})
	db.p <- u
}

// 更新数组元素
func (db *DBCache) SetArray(table string, id bson.ObjectId, i interface{}, name string, qKey string, qI interface{}) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	} ()
	u := &updaterData{table, id, nil, "", false, false, nil}
	u.arrays = append(u.arrays, &updateArray{"$set", name + ".$", i, name + "." + qKey, qI})
	db.p <- u
}

func (db *DBCache) _updateId(d *updaterData) {
	if t, ok := db.updateIds[d.table]; ok {
		for _, u := range t {
			if u.id == d.id {
				u.Update(d.i, d.name)
				if d.isRemove {
					u.isRemove = d.isRemove
				}
				if d.isInsert {
					u.isInsert = d.isInsert
				}
				if len(d.arrays) > 0 {
					u.arrays = append(u.arrays, d.arrays...)
				}
				return
			}
		}

		u := &DBUpdater{d.id, make(map[string]interface{}), nil, d.isRemove, d.isInsert, nil}
		u.Update(d.i, d.name)
		if len(d.arrays) > 0 {
			u.arrays = append(u.arrays, d.arrays...)
		}
		db.updateIds[d.table] = append(t, u)
	} else {
		u := &DBUpdater{d.id, make(map[string]interface{}), nil, d.isRemove, d.isInsert, nil}
		u.Update(d.i, d.name)
		if len(d.arrays) > 0 {
			u.arrays = append(u.arrays, d.arrays...)
		}
		t := make([]*DBUpdater, 0, 20)
		t= append(t, u)
		db.updateIds[d.table] = t
	}
	// fmt.Println(db.updateIds)
}

func (db *DBCache) Flush() {
	db.flushSig <- true
}
func (db *DBCache) _flush() {
	if 0 == len(db.updateIds) {
		return
	}

	d, dbsession, err := GetDB()
	if err != nil {
		return
	}
	defer dbsession.Close()
	for k, t := range db.updateIds {
		if len(t) > 0 {
			c := d.C(k)
			b := c.Bulk()
			count := 0
			for _, v := range t {
				// fmt.Println("save table", k, v.id)
				if v.isRemove {
					b.Remove(bson.M{"_id": v.id})
					count ++
				} else if nil != v.i {
					if v.isInsert {
						b.Insert(v.i)
					} else {
						b.Update(bson.M{"_id": v.id}, v.i)
					}
					count ++
				} else {
					if len(v.updater) > 0 {
						vals := bson.M{}
						for n, u := range v.updater {
							vals[n] = u
						}
						b.Update(bson.M{"_id": v.id}, bson.M{"$set": vals})
						count ++
					}
					if len(v.arrays) > 0 {
						for _, a := range v.arrays {
							m1, m2 := a.sql(v.id)
							b.Update(m1, m2)
							count ++
						}
					}
				}
				// 需要写入的内容已经很多了
				if count > 600 {
					_, err := b.Run()
					if nil != err {
						fmt.Println("更新数据库发生错误", k, err)
					}
					b = c.Bulk()
					count = 0
				}
			}
			_, err := b.Run()
			if nil != err {
				fmt.Println("更新数据库发生错误", k, err)
			} else {
				// fmt.Println("更新数据库成功", k, ret)
			}
		}
	}

	db.updateIds = make(map[string][]*DBUpdater)
}

func (db *DBCache) Close() {
	fmt.Println("正在关闭数据库存储缓冲")
	close(db.p)
	<- db.closeSig
	fmt.Println("完成关闭数据库存储缓冲")
}

var DB = NewDBCache()

func Insert(cname string, id bson.ObjectId, i interface{}) {
	DB.Insert(cname, id, i)
}

// 更新数据表，cname: 表名, id: 数据标识，i: 更新后的数据，name: 更新的字段名，如果传空值则是更新整个表
func UpdateId(cname string, id bson.ObjectId, i interface{}, name string) {
	DB.UpdateId(cname, id, i, name)
}

func RemoveId(cname string, id bson.ObjectId) {
	DB.RemoveId(cname, id)
}

// 添加数组元素，i: 要添加的元素，arrayName: 数组的字段名
func PushArray(cname string, id bson.ObjectId, i interface{}, arrayName string) {
	DB.PushArray(cname, id, i, arrayName)
}

// 删除数组元素，i: 要删除元素的查询语句，例如
// PullArray("table", c.Id_, bson.M{"playerid": playerId}, "members")
func PullArray(cname string, id bson.ObjectId, i interface{}, arrayName string) {
	DB.PullArray(cname, id, i, arrayName)
}

// 更新数组元素，i: 要更新的元素，qKey: 要更新的元素字段名，qI: 更新后的值
func SetArray(cname string, id bson.ObjectId, i interface{}, arrayName string, qKey string, qI interface{}) {
	DB.SetArray(cname, id, i, arrayName, qKey, qI)
}

func Flush() {
	DB.Flush()
}

func Close() {
	DB.Close()
}

// 获取数据，query 为查询语句，createFunc 为创建数据函数，在查询语句查询失败后调用进行创建新数据的方法
// ret 接收数据的结构体
func GetDataWithCreate(cname string, query interface{}, ret interface{}, createFunc func()(bson.ObjectId, interface{})) (interface{}, error) {
	mdb, dbsession, err := GetDB()
	if err != nil {
		return nil, err
	}
	defer dbsession.Close()
	c := mdb.C(cname)
	err = c.Find(query).One(ret)
	if mgo.ErrNotFound == err {
		if nil != createFunc {
			id, data := createFunc()
			Insert(cname, id, data)
			return data, nil
		}
	} else if nil == err {
		return ret, nil
	}
	return nil, err
}

// 自动增长唯一id
type AutoUUID struct {
	Id_             bson.ObjectId `bson:"_id"`
	Uuid			uint32	
}

var autoId bson.ObjectId
var autoChan chan uint32

func NewAutoUUID() *AutoUUID {
	db, dbsession, err := GetDB()
	if nil != err {
		return nil
	}
	defer dbsession.Close()
	c := db.C(AUTOUUID)
	var uuid AutoUUID
	err = c.Find(nil).One(&uuid)
	if nil != err {
		uuid = AutoUUID {
			bson.NewObjectId(),
			0,
		}
		err = c.Insert(&uuid)
		if nil != err {
			return nil
		} else {
			autoId = uuid.Id_
		}
	} else {
		autoId = uuid.Id_
	}

	autoChan = make(chan uint32)

	go func() {
		for {
			uuid.Uuid++
			DB.UpdateId(AUTOUUID, uuid.Id_, uuid, "")
			autoChan <- uuid.Uuid
		}
	} ()

	return &uuid
}

func GetUUID() uint32 {
	return <- autoChan
}

func GetUID() int32 {
	return int32(<- autoChan)
}
